import math
import requests
import os
from multiprocessing import Pool


####################################
#  文件处理
####################################
class FileUtils:
    rootPath = None
    logFileName = None

    def __init__(self, rootPath):
        self.rootPath = rootPath
        self.logFileName = '/index.txt'

    # 判断视频是否已缓存
    def isDownloaded(self, name):
        filePath = self.rootPath + '/' + name
        return os.path.exists(filePath)

    # 初始化视频文件夹，创建视频信息记录文件
    def initFolder(self, name, url):
        name = name.replace('/', '_')
        name = name.replace('\\', '_')
        name = name.replace('//', '_')
        if (self.isDownloaded(name)):
            return False
        path = self.rootPath + '/' + name
        os.mkdir(path)
        info = VideoInfo()
        video = info.getVideoDetail(url)
        file = open(path + self.logFileName, mode='w', encoding='utf-8')
        for key, value in video.items():
            file.write(str(value) + '\n')
        file.close()
        return path

    # 保存视频信息
    def saveVideoInfo(self, path, name):
        file = open(path + self.logFileName, mode='a', encoding='utf-8')
        file.write(name + '\n')
        file.close()

    # 获取缓存的视频信息
    def getCacheVideoInfo(self):
        videos = []
        for videoDir in os.listdir(self.rootPath):
            logPath = self.rootPath + '/' + videoDir + self.logFileName
            file = open(logPath, 'r', encoding='utf-8')
            values = file.readlines()
            video = {}
            video['name'] = values[0].strip('\n')
            video['date'] = values[1].strip('\n')
            video['auth'] = values[2].strip('\n')
            video['imgUrl'] = values[3].strip('\n')
            video['parts'] = []
            for i in range(4, len(values)):
                video['parts'].append(values[i].strip('\n'))
            videos.append(video)
        return videos


####################################
#  根据关键词搜索视频，返回地址
####################################
class Search:
    url = 'https://api.bilibili.com/x/web-interface/search/type?context=&page=1&order=click&keyword=?????&duration=4&tids_2=&__refresh__=true&_extra=&search_type=video&tids=0&highlight=1&single_column=0'

    # 通过关键词搜索视频
    def getSearchVideoList(self, keyword):
        videoList = []
        url = self.url.replace('?????', keyword)
        res = requests.get(url).json().get('data').get('result')
        for data in res:
            video = {}
            video['name'] = data.get('title')
            video['name'] = video['name'].replace('<em class="keyword">', '')
            video['name'] = video['name'].replace('</em>', '')
            video['play'] = data.get('play')
            video['duration'] = data.get('duration')
            video['date'] = data.get('pubdate')
            video['auth'] = data.get('author')
            video['imgUrl'] = 'https://' + data.get('pic')
            video['link'] = 'https://www.bilibili.com/video/' + data.get('bvid')
            videoList.append(video)
        return videoList

    # 通过视频url获取视频详细信息
    def getVideoDetail(self, url):
        videoList = []
        bvid = url.split('/')[-1]
        url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + bvid
        head = {'Referer': 'https://www.bilibili.com',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
        res = requests.get(url, headers=head)
        data = res.json().get('data')
        for item in data:
            videoList.append(item['part'])
        return videoList


####################################
#  根据视频地址，获取下载所需的详细信息
####################################
class VideoInfo:

    # 通过bvid转换avid
    def getAvid(self, bvid):
        BvNo1 = bvid[2:]
        keys = {
            '1': '13', '2': '12', '3': '46', '4': '31', '5': '43', '6': '18', '7': '40', '8': '28', '9': '5',
            'A': '54', 'B': '20', 'C': '15', 'D': '8', 'E': '39', 'F': '57', 'G': '45', 'H': '36', 'J': '38', 'K': '51',
            'L': '42', 'M': '49', 'N': '52', 'P': '53', 'Q': '7', 'R': '4', 'S': '9', 'T': '50', 'U': '10', 'V': '44',
            'W': '34', 'X': '6', 'Y': '25', 'Z': '1',
            'a': '26', 'b': '29', 'c': '56', 'd': '3', 'e': '24', 'f': '0', 'g': '47', 'h': '27', 'i': '22', 'j': '41',
            'k': '16', 'm': '11', 'n': '37', 'o': '2',
            'p': '35', 'q': '21', 'r': '17', 's': '33', 't': '30', 'u': '48', 'v': '23', 'w': '55', 'x': '32',
            'y': '14',
            'z': '19'
        }
        # 2. 将key对应的value存入一个列表
        BvNo2 = []
        for index, ch in enumerate(BvNo1):
            BvNo2.append(int(str(keys[ch])))
        # 3. 对列表中不同位置的数进行*58的x次方的操作
        BvNo2[0] = int(BvNo2[0] * math.pow(58, 6))
        BvNo2[1] = int(BvNo2[1] * math.pow(58, 2))
        BvNo2[2] = int(BvNo2[2] * math.pow(58, 4))
        BvNo2[3] = int(BvNo2[3] * math.pow(58, 8))
        BvNo2[4] = int(BvNo2[4] * math.pow(58, 5))
        BvNo2[5] = int(BvNo2[5] * math.pow(58, 9))
        BvNo2[6] = int(BvNo2[6] * math.pow(58, 3))
        BvNo2[7] = int(BvNo2[7] * math.pow(58, 7))
        BvNo2[8] = int(BvNo2[8] * math.pow(58, 1))
        BvNo2[9] = int(BvNo2[9] * math.pow(58, 0))
        # 4.求出这10个数的合
        sum = 0
        for i in BvNo2:
            sum += i
        # 5. 将和减去100618342136696320
        sum -= 100618342136696320
        # 6. 将sum 与177451812进行异或
        temp = 177451812
        return sum ^ temp


    # 获取视频集列表
    def getVideoList(self, bvid):
        url = 'https://api.bilibili.com/x/player/pagelist?bvid=' + bvid
        head = {'Referer': 'https://www.bilibili.com',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
        res = requests.get(url, headers=head)
        data = res.json().get('data')
        videoList = []
        for value in data:
            videoList.append({'name': value['part'], 'cid': str(value['cid'])})
        return videoList

    # 获取每集视频下载信息
    def getVideoInfo(self, avid, cid, qn):
        url = 'https://api.bilibili.com/x/player/playurl?avid=' + avid + '&cid=' + cid + '&qn=' + qn
        head = {'Referer': 'https://www.bilibili.com',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
                'cookie': "buvid3=FB54E8DB-138A-07B5-5453-D5631F30702460094infoc; CURRENT_FNVAL=80; _uuid=4E1C0BDE-2831-6700-0B2E-8D8FF1CD4DA761738infoc; blackside_state=1; DedeUserID=389316778; DedeUserID__ckMd5=47206f5be4cd6563; SESSDATA=18907523%2C1624372463%2Ccf742*c1; bili_jct=f981246f38b80cfb3359535f4b3672e9; rpdid=|(k|m|mul|mu0J'uY|))kRkJ); CURRENT_QUALITY=80; bp_t_offset_389316778=474195987560782361; sid=a489hi2a; PVID=4; bfe_id=5112800f2e3d3cf17a473918472e345c"}
        res = requests.get(url, headers=head)
        info = res.json().get('data').get('durl')[0]
        url = info.get('url')
        size = info.get('size')
        videoInfo = {'url': url, 'size': str(size)}
        return videoInfo

    # 通过url获取所有视频的下载信息
    def getVideos(self, url):
        bvid = url.split('/')[-1]
        avid = str(self.getAvid(bvid))
        videoList = self.getVideoList(bvid)
        for video in videoList:
            videoInfo = self.getVideoInfo(avid, video['cid'], '80')
            video.update(videoInfo)
        return videoList

    # 获取视频详细信息
    def getVideoDetail(self, url):
        bvid = url.split('/')[-1]
        url = 'https://api.bilibili.com/x/web-interface/view?bvid=' + bvid
        head = {'Referer': 'https://www.bilibili.com',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36'}
        data = requests.get(url, headers=head).json().get('data')
        video = {}
        video['name'] = data.get('title')
        video['date'] = data.get('pubdate')
        video['auth'] = data.get('owner').get('name')
        video['imgUrl'] = data.get('pic')
        return video


####################################
#  下载单个视频
####################################
class DownloadVideo:
    video = None
    path = None
    processNum = None

    def __init__(self, video, path, processNum):
        self.video = video
        self.processNum = processNum
        os.mkdir(path + '/' + video.get('name'))
        self.path = path + '/' + video.get('name') + '/' + self.video.get('name') + '.flv'

    # 开始下载视频
    def start(self):
        unitSize = int(int(self.video.get('size')) / self.processNum)
        p = Pool(self.processNum)
        for i in range(0, self.processNum):
            start = i * unitSize
            if i == self.processNum - 1:
                end = int(self.video.get('size'))
            else:
                end = start + unitSize - 1
            p.apply_async(func=self.downloadUnitProcess, args=(self.path + str(i), self.video.get('url'), start, end))
        p.close()
        p.join()
        self.mergeVideo()

    # 每个视频片段下载进程
    def downloadUnitProcess(self, path, url, start, end):
        header = {
            'Referer': 'https://www.bilibili.com',
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36',
            'Range': 'bytes=' + str(start) + '-' + str(end)
        }
        res = requests.get(url, headers=header, stream=True)
        videoFile = open(path, 'wb')

        for chunk in res.iter_content(chunk_size=4096):
            videoFile.write(chunk)

        videoFile.close()
        return True

    # 将下载的多个视频文件合并
    def mergeVideo(self):
        with open(self.path, 'wb') as finalVideo:
            for i in range(0, self.processNum):
                with open(self.path + str(i), 'rb') as tempVideo:
                    finalVideo.write(tempVideo.read())
                    tempVideo.close()
                    os.remove(self.path + str(i))